package com.xuzhiguang.lightnat.client.control;

import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import com.xuzhiguang.lightnat.client.Client;
import com.xuzhiguang.lightnat.client.common.handler.ExceptionHandler;
import com.xuzhiguang.lightnat.client.common.handler.IdleCheckHandler;
import com.xuzhiguang.lightnat.client.common.handler.KeepAliveHandler;
import com.xuzhiguang.lightnat.client.control.handler.InactiveHandler;
import com.xuzhiguang.lightnat.common.codec.NatFrameDecoder;
import com.xuzhiguang.lightnat.common.codec.NatFrameEncoder;
import com.xuzhiguang.lightnat.common.codec.NatMessageDecoder;
import com.xuzhiguang.lightnat.common.codec.NatMessageEncoder;
import com.xuzhiguang.lightnat.common.message.MessageProcessorFactory;
import com.xuzhiguang.lightnat.common.message.MessageProcessorHandler;
import com.xuzhiguang.lightnat.common.message.NatMessage;
import com.xuzhiguang.lightnat.common.message.control.ControlAuthenticationRequest;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ControlClient implements Client {

    private final ControlClientProperties controlClientProperties;

    private final MessageProcessorFactory messageProcessorFactory;

    private NioEventLoopGroup group;

    private Bootstrap bootstrap;

    private Channel channel;

    public ControlClient(ControlClientProperties controlClientProperties, MessageProcessorFactory messageProcessorFactory) {
        this.controlClientProperties = controlClientProperties;
        this.messageProcessorFactory = messageProcessorFactory;
    }

    @Override
    public void start() {

        checkProperties();

        group = new NioEventLoopGroup(new DefaultThreadFactory("c-client"));
        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.INFO);
        MessageProcessorHandler messageProcessorHandler = new MessageProcessorHandler(messageProcessorFactory);
        KeepAliveHandler keepAliveHandler = new KeepAliveHandler();
        ExceptionHandler exceptionHandler = new ExceptionHandler();
        InactiveHandler inactiveHandler = new InactiveHandler();

        bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(loggingHandler)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline()
                                .addLast("idleCheckHandler", new IdleCheckHandler())
                                .addLast("natFrameDecoder", new NatFrameDecoder())
                                .addLast("natFrameEncoder", new NatFrameEncoder())
                                .addLast("natMessageDecoder", new NatMessageDecoder())
                                .addLast("natMessageEncoder", new NatMessageEncoder())
                                .addLast("keepAliveHandler", keepAliveHandler)
                                .addLast("inactiveHandler", inactiveHandler)
                                .addLast("messageProcessorHandler", messageProcessorHandler)
                                .addLast("exceptionHandler", exceptionHandler);
                    }
                });

        this.connect();
    }

    public boolean connect() {
        ChannelFuture f = null;
        try {
            f = bootstrap.connect(controlClientProperties.getControlServerHost(), controlClientProperties.getControlServerPort()).sync();
        } catch (InterruptedException e) {
            log.info("control client connect fail.", e);
            Thread.currentThread().interrupt();
            return false;
        }

        if (f.isSuccess()) {
            this.channel = f.channel();
            this.auth(this.channel);
        }
        return f.isSuccess();
    }

    @Override
    public void stop() {
        this.channel.close();
        this.group.shutdownGracefully();
    }

    private void auth(Channel channel) {
        ControlAuthenticationRequest request = new ControlAuthenticationRequest();
        request.setToken(this.controlClientProperties.getControlToken());
        NatMessage natMessage = new NatMessage(request);
        channel.writeAndFlush(natMessage);
    }

    private void checkProperties() {

        if (StrUtil.isEmptyIfStr(controlClientProperties.getControlServerHost())) {
            throw new IllegalArgumentException("control server host is null");
        }

        if (!NetUtil.isValidPort(controlClientProperties.getControlServerPort())) {
            throw new IllegalArgumentException("control server port is illegal. server port:" + controlClientProperties.getControlServerPort());
        }

        if (StrUtil.isEmptyIfStr(controlClientProperties.getControlToken())) {
            throw new IllegalArgumentException("transfer token is null");
        }
    }
}
